package org.gpp.proj1.logic.core.client;

import java.rmi.RemoteException;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import org.gpp.proj1.communication.CommunicationException;
import org.gpp.proj1.communication.ConnectionException;
import org.gpp.proj1.communication.listener.ConnectionAttemptListener;
import org.gpp.proj1.communication.listener.OfflineClientListener;
import org.gpp.proj1.communication.listener.RequestListener;
import org.gpp.proj1.communication.manager.NetworkManager;
import org.gpp.proj1.communication.manager.RMINetworkManager;
import org.gpp.proj1.communication.protocol.Message;
import org.gpp.proj1.communication.protocol.ProtocolIdentifiers;
import org.gpp.proj1.communication.protocol.ProtocolMessageFactory;
import org.gpp.proj1.communication.protocol.ProtocolMessageParser;
import org.gpp.proj1.communication.protocol.ProtocolParameters;
import org.gpp.proj1.logic.core.client.init.GameInitThread;
import org.gpp.proj1.ui.controller.ISRGUIController;

import com.jme.util.LoggingSystem;

public class Client implements RequestListener, OfflineClientListener {

	/**
	 * The maximum number of incoming messages in the incoming messages
	 * queue
	 */
	private static final int INCOMING_MESSAGES_CAPACITY = 100;
	
	private NetworkManager networkManager;

	private final String alias;

	private final int port;

	private boolean isConnected;

	private ConnectionAttemptListener connectionAttemptListener;

	private final ISRGUIController controller;

	private String serverId;

	/**
	 * The incoming messages queue
	 */
	private BlockingQueue<Message> incomingMessages;

	private ClientRequestHandler requestHandler;

	private String clientId;


	public Client( ISRGUIController controller, String alias, int clientPort ) {

		this.controller = controller;
		this.alias = alias;
		this.port = clientPort;
		this.isConnected = false;
		this.connectionAttemptListener = null;
		
		this.incomingMessages = new ArrayBlockingQueue<Message>( INCOMING_MESSAGES_CAPACITY );
		
		this.requestHandler = new ClientRequestHandler( incomingMessages );
	}

	public void connect( String serverHost, int serverPort ) throws CommunicationException {

		try {
			this.networkManager = new RMINetworkManager( port );

			this.networkManager.registerRequestListener( this );
			this.networkManager.registerOfflineClientListener( this );

			this.serverId = this.networkManager.connectTo( serverHost, serverPort );
			this.clientId = this.networkManager.getClientIdFor( serverId );

			this.isConnected = true;
			if( this.connectionAttemptListener != null ) {
				this.connectionAttemptListener.connectionAttemptSucceeded();
			}

			this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildHelloMessage( this.alias ) );
			
			new Thread( requestHandler ).start();
		} catch (ConnectionException e) {
			throw new CommunicationException( "Could not connect to server", e );
		} catch (RemoteException e) {
			throw new CommunicationException( "Could not connect to server", e );
		}
	}

	public void disconnect() {

		this.isConnected = false;
		this.networkManager.stop();
	}

	public void stopConnectionAttempt() {
		// TODO Auto-generated method stub

	}

	public void setConnectionAttemptListener(ConnectionAttemptListener listener) {

		this.connectionAttemptListener = listener;
	}

	public boolean isConnected() {

		return isConnected;
	}

	public void sendChat(String text) {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildChatMessage( this.alias, text ) );
	}

	public void requestReceived(Message message) {
		System.err.println("MESSAGE RECEIVED: " + message);

		this.incomingMessages.offer( message );
	}

	public void toggleReadinessState() {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildToggleReadinessMessage() );
	}

	public void clientIsOffline(String clientId) {

		// Server is disconnected?
		if( clientId.equals( serverId ) ) {

			this.isConnected = false;
			this.controller.notifyDisconnected();
		} else {

			this.controller.notifyClientDisconnected( clientId );
		}
	}
	
	public String getCurrentPlayerId() {
		
		return this.clientId;
	}

	private class ClientRequestHandler implements Runnable {

		private BlockingQueue<Message> messages;

		private boolean stopRunning = false;

		private Map<String, String> aliasMap;

		/**
		 * Creates a new RequestHandler instance
		 * @param messages The queue the handler will take messages from
		 */
		public ClientRequestHandler( BlockingQueue<Message> messages ) {

			this.messages = messages;
		}

		/**
		 * Stops this thread
		 */
		public void stopRunning() {
			
			this.stopRunning = true;			
		}

		public void run() {

			while( ! stopRunning  ) {
				try {
					Message message = messages.take();

					if( message.getIdentifier().equals( ProtocolIdentifiers.CHAT ) ) {

						String alias = message.getParameters()[ProtocolParameters.CHAT_MESSAGE_ALIAS];
						String chatMessage = message.getParameters()[ProtocolParameters.CHAT_MESSAGE_TEXT];
						controller.notifyChatMessage( alias, chatMessage ); 
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.WHOSHERE ) ) {

						aliasMap = ProtocolMessageParser.parseAliasMap( message );
						controller.notifyWhosHereMessage( aliasMap, ProtocolMessageParser.parseReadySet( message ) );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.CHANGEMAP ) ) {

						controller.notifyChangeMap( message.getParameters()[ ProtocolParameters.CHANGEMAP_MESSAGE_MAP_NAME ] );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.SERVERNAME ) ) {						

						controller.notifyServerName( message.getParameters()[ ProtocolParameters.SERVERNAME_MESSAGE_SERVER_NAME ] );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.CLIENTOFFLINE ) ) {

						clientIsOffline( message.getParameters()[ ProtocolParameters.CLIENTOFFLINE_MESSAGE_ID ] );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.INITPHASE ) ) {

						// Starting initialization thread
						GameInitThread gameInitThread = new GameInitThread( clientId, serverId, alias, aliasMap, controller, networkManager );

						new Thread( gameInitThread ).start();
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.CURRENTTURN ) ) {
						
						String playerId = message.getParameters()[ ProtocolParameters.CURRENTTURN_PLAYER_ID ];
						controller.notifyCurrentTurn( playerId, this.aliasMap.get( playerId ) );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.YOURTURN ) ) {
						
						controller.notifyYourTurn();
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.CURRENTTURNROBOT ) ) {
							
						controller.notifyTurnRobot( message.getParameters()[ ProtocolParameters.CURRENTTURNROBOT_ROBOT_NAME ] );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.ENDTURN ) ) {
						
						networkManager.sendMessage( serverId, ProtocolMessageFactory.buildWhoseTurnIsIt() );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.ROBOTMOVED ) ) {
						
						String player = message.getParameters()[0];
						String robot = message.getParameters()[1];
						int x = Integer.parseInt( message.getParameters()[2] );
						int y = Integer.parseInt( message.getParameters()[3] );
						
						controller.notifyRobotMoved( player, robot, x, y );
					} else if( message.getIdentifier().equals( ProtocolIdentifiers.PLAYERATTACKED ) ) {
					
						String player = message.getParameters()[0];
						String robot = message.getParameters()[1];
						int xTarget = Integer.parseInt( message.getParameters()[2] );
						int yTarget = Integer.parseInt( message.getParameters()[3] );
						String part = message.getParameters()[4];
						String attack = message.getParameters()[5];
						
						controller.notifyRobotAttacked( player, robot, xTarget, yTarget, part, attack );
					}
				} catch (InterruptedException e) {
					LoggingSystem.getLogger().warning( "Failed to handle incoming request" );
				}
			}
		}		
	}

	public void notifyReadyToStartGame() {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildWhoseTurnIsIt() );
	}

	public void notifyEndTurn() {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildEndTurn() );
	}

	public void movedRobot(String robot, int newX, int newY) {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildMovedRobotMessage( robot, newX, newY ) );
	}

	public void robotAttacked(String robot, int selectedX, int selectedY, String selectedPart, String selectedAttack) {

		this.networkManager.sendMessage( serverId, ProtocolMessageFactory.buildRobotAttackedMessage(robot, selectedX, selectedY, selectedPart, selectedAttack) );
	}
}